#include "multi_button.h"
 
#define EVENT_CB(ev)   if(handle->cb[ev])handle->cb[ev]((void*)handle)
#define PRESS_REPEAT_MAX_NUM  15 /*!< The maximum value of the repeat counter */
 
static struct ButtonPara buttonpara;
//button handle list head.
static struct Button* head_handle = NULL;
 
static void button_handler(struct Button* handle);
 
 
void button_para_init(struct ButtonPara para)
{
  buttonpara.ticks_interval = para.ticks_interval;
  buttonpara.debounce_ticks = para.debounce_ticks;
  buttonpara.short_ticks = para.short_ticks;
  buttonpara.long_ticks = para.long_ticks;
}
/**
  * @brief  Initializes the button struct handle.
  * @param  handle: the button handle struct.
  * @param  pin_level: read the HAL GPIO of the connected button level.
  * @param  active_level: pressed GPIO level.
  * @param  button_id: the button id.
  * @retval None
  */
void button_init(struct Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id)
{
  memset(handle, 0, sizeof(struct Button));
  handle->event = (uint8_t)NONE_PRESS;
  handle->hal_button_Level = pin_level;
  handle->button_level = handle->hal_button_Level(button_id);
  handle->active_level = active_level;
  handle->button_id = button_id;
}
 
/**
  * @brief  Attach the button event callback function.
  * @param  handle: the button handle struct.
  * @param  event: trigger event type.
  * @param  cb: callback function.
  * @retval None
  */
void button_attach(struct Button* handle, PressEvent event, BtnCallback cb)
{
  handle->cb[event] = cb;
}
 
/**
  * @brief  Inquire the button event happen.
  * @param  handle: the button handle struct.
  * @retval button event.
  */
PressEvent get_button_event(struct Button* handle)
{
  return (PressEvent)(handle->event);
}
 
/**
  * @brief  Button driver core function, driver state machine.
  * @param  handle: the button handle struct.
  * @retval None
  */
static void button_handler(struct Button* handle)
{
  uint8_t read_gpio_level = handle->hal_button_Level(handle->button_id);
 
  //ticks counter working..
  if((handle->state) > 0) handle->ticks++;
 
  /*------------button debounce handle---------------*/
  if(read_gpio_level != handle->button_level) { //not equal to prev one
    //continue read 3 times same new level change
    if(++(handle->debounce_cnt) >= buttonpara.debounce_ticks) {
      handle->button_level = read_gpio_level;
      handle->debounce_cnt = 0;
    }
  } else { //level not change ,counter reset.
    handle->debounce_cnt = 0;
  }
 
  /*-----------------State machine-------------------*/
  switch (handle->state) {
  case 0:
    if(handle->button_level == handle->active_level) {  //start press down
      handle->event = (uint8_t)PRESS_DOWN;
      EVENT_CB(PRESS_DOWN);
      handle->ticks = 0;
      handle->repeat = 1;
      handle->state = 1;
    } else {
      handle->event = (uint8_t)NONE_PRESS;
    }
    break;
 
  case 1:
    if(handle->button_level != handle->active_level) { //released press up
      handle->event = (uint8_t)PRESS_UP;
      EVENT_CB(PRESS_UP);
      handle->ticks = 0;
      handle->state = 2;
    } else if(handle->ticks > buttonpara.long_ticks) {
      handle->event = (uint8_t)LONG_PRESS_START;
      EVENT_CB(LONG_PRESS_START);
      handle->state = 5;
    }
    break;
 
  case 2:
    if(handle->button_level == handle->active_level) { //press down again
      handle->event = (uint8_t)PRESS_DOWN;
      EVENT_CB(PRESS_DOWN);
      if(handle->repeat != PRESS_REPEAT_MAX_NUM) {
        handle->repeat++;
      }
      EVENT_CB(PRESS_REPEAT); // repeat hit
      handle->ticks = 0;
      handle->state = 3;
    } else if(handle->ticks > buttonpara.short_ticks) { //released timeout
      if(handle->repeat == 1) {
        handle->event = (uint8_t)SINGLE_CLICK;
        EVENT_CB(SINGLE_CLICK);
      } else if(handle->repeat == 2) {
        handle->event = (uint8_t)DOUBLE_CLICK;
        EVENT_CB(DOUBLE_CLICK); // repeat hit
      }
      handle->state = 0;
    }
    break;
 
  case 3:
    if(handle->button_level != handle->active_level) { //released press up
      handle->event = (uint8_t)PRESS_UP;
      EVENT_CB(PRESS_UP);
      if(handle->ticks < buttonpara.short_ticks) {
        handle->ticks = 0;
        handle->state = 2; //repeat press
      } else {
        handle->state = 0;
      }
    } else if(handle->ticks > buttonpara.short_ticks) { // SHORT_TICKS < press down hold time < LONG_TICKS
      handle->state = 1;
    }
    break;
 
  case 5:
    if(handle->button_level == handle->active_level) {
      //continue hold trigger
      if(handle->ticks > buttonpara.long_ticks) {
      handle->event = (uint8_t)LONG_PRESS_HOLD;
      EVENT_CB(LONG_PRESS_HOLD);
      handle->ticks = 0;
      }
    } else { //released
      handle->event = (uint8_t)PRESS_UP;
      EVENT_CB(PRESS_UP);
      handle->state = 0; //reset
    }
    break;
  default:
    handle->state = 0; //reset
    break;
  }
}
 
/**
  * @brief  Start the button work, add the handle into work list.
  * @param  handle: target handle struct.
  * @retval 0: succeed. -1: already exist.
  */
int button_start(struct Button* handle)
{
  struct Button* target = head_handle;
  while(target) {
    if(target == handle) return -1;  //already exist.
    target = target->next;
  }
  handle->next = head_handle;
  head_handle = handle;
  return 0;
}
 
/**
  * @brief  Stop the button work, remove the handle off work list.
  * @param  handle: target handle struct.
  * @retval None
  */
void button_stop(struct Button* handle)
{
  struct Button** curr;
  for(curr = &head_handle; *curr; ) {
    struct Button* entry = *curr;
    if(entry == handle) {
      *curr = entry->next;
//      free(entry);
      return;//glacier add 2021-8-18
    } else {
      curr = &entry->next;
    }
  }
}
 
/**
  * @brief  background ticks, timer repeat invoking interval 5ms.
  * @param  None.
  * @retval None
  */
void button_ticks(void)
{
  struct Button* target;
  for(target=head_handle; target; target=target->next) {
    button_handler(target);
  }
}